package triangle.dj;

import java.io.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;

import jxl.*;
import jxl.write.*;
import triangle.datastructures.*;
import triangle.utils.*;

/**
 * DjGenerator for Triangle. 
 * This class requires two files - the template file, the seniority file and the rules file.
 * Template file just reads and fills in the names, saves it as the next Sunday coming up. (DJ_3_12_12.xls)
 * Seniority file lists seniority of the members. 
 * The user could just put it in manually or give in the file.
 * Rules file lists when each job is to be done in which day.
 * The entire program assumes DJ program won't change too much in the future, but as long as the format doesn't change
 * it should be flexible to add or remove djs.
 * The first line is the start of week, the rest is line:column,column,column that dj should be done.
 * 
 * Also Jason usually ignores coding criteria. Seeing that this code may be used by some other person in the future,
 * Jason is being generous and coding it as nicely as he can... but grad students are known to write horendous code.
 * 
 * @author hcho33
 *
 */
public class DjGenerator {

	private String templateExcelFilename = "djtemplate.xls";
	private final String SENIORITYFILENAME = "memberslist.txt";
	private final int INITIALIDX = 0;
	private final int LIVESTATUSIDX = 1;
	// constants on templatedj 
	private final int STARTASSIGNCOL = 2;
	private final int STARTASSIGNROW = 1;
	private final int WEEKENDCOLIDX = 6;
	
	private boolean markSundayAsPledge = true;
	
	public enum MemberStatus {SEATED, UNSEATED, PLEDGE};
	public enum MemberLiving {IN, OUT};
	public enum MSL {SI,UI,PI,SO,UO,PO};
	
	// initial : (memberstatus, in/out)
	private HashMap<String, MSL> memberStatus = new HashMap<String, MSL>();
	// memberstatus : initials
	private HashMapVector<MSL, String> invertedMemberStatus = new HashMapVector<MSL, String>();
	// stores row-column of where DJ should go.
	private HashMapSet<Integer, Integer> rowColumnDjs = new HashMapSet<Integer, Integer>();
	// stores column-row of where DJ should go.
	private HashMapSet<Integer, Integer> columnRowDjs = new HashMapSet<Integer, Integer>();
	// stores whether the dj on row-column has been assigned.
	private MultiHashMap<Integer, String> djAssigned = new MultiHashMap<Integer, String>();
	// seniority of the members in descending order
	private Vector<String> memberSeniorityOrder = new Vector<String>();
	// if it's living week, pledges assigned DJ the same as actives are during the week.
	private boolean pledgeAsActive = false;
	
	public DjGenerator(){}
	
	public DjGenerator(boolean markSundayAsPledge)
	{
		this.markSundayAsPledge = markSundayAsPledge;
	}
	
	public DjGenerator(String templateDjFilename)
	{
		this.templateExcelFilename = templateDjFilename;
	}
	
	public DjGenerator(String templateDjFilename, boolean markSundayAsPledge)
	{
		this.templateExcelFilename = templateDjFilename;
		this.markSundayAsPledge = markSundayAsPledge;
	}
	
	public String getTemplate()
	{
		return templateExcelFilename;
	}
	
	public void generateDj()
	{
		getRandomizedDj(loadDjFile());
		System.out.println(this.invertedMemberStatus);

	}
	
	public void generateDj(boolean pledgeAsActive)
	{
		this.pledgeAsActive = pledgeAsActive;
		getRandomizedDj(loadDjFile());
		System.out.println(this.invertedMemberStatus);
	}
	
	private Workbook loadDjFile()
	{
		try {
			Workbook workbook = Workbook.getWorkbook(new File(templateExcelFilename));
			return workbook;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			return null;
		} 
	}
	
	private void getRandomizedDj(Workbook templateDj)
	{
		try
		{
			String nextDj = getNextDj();
			System.out.println(nextDj);
			String newWorkBookFilename = "dj-" + nextDj + ".xls";
			WritableWorkbook newWorkbook = Workbook.createWorkbook(new File(newWorkBookFilename), templateDj);
			
			populateSeniority();
			populateRules(templateDj);
			populateDjAssigned();
			writeDj(newWorkbook);
			newWorkbook.write();
			newWorkbook.close();
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	private String getNextDj() {
		DateFormat dateFormat = new SimpleDateFormat("MM-dd-yy");
		Calendar calendar = Calendar.getInstance();
		int weekday = calendar.get(Calendar.DAY_OF_WEEK);
		int day = Calendar.SUNDAY - weekday;
		
		Date date = new Date();
		calendar.add(Calendar.DAY_OF_YEAR, day);
		date.setTime(calendar.getTimeInMillis());
		String nextDj = dateFormat.format(date);
		return nextDj;
	}

	private void writeDj(WritableWorkbook newWorkbook) {
		// TODO Auto-generated method stub
		WritableSheet sheet = newWorkbook.getSheet(0);
		WritableCell cell = null;
		for (Integer row : this.djAssigned.keySet())
		{
			HashMap<Integer, String> currRow = this.djAssigned.get(row);
			for (Integer col : currRow.keySet())
			{
				cell = sheet.getWritableCell(col, row);
				if (cell.getType() == CellType.LABEL)
				{
					Label l = (Label) cell;
					l.setString(currRow.get(col));
				}
			}
		}
		cell = sheet.getWritableCell(1, 0);
		if (cell.getType() == CellType.LABEL)
		{
			String nextDj = getNextDj();
			Label l = (Label) cell;
			l.setString("DJs Week of " + nextDj);
		}
	}

	private void populateDjAssigned() {
		
		int seatedDjCount = 0;
		while (!columnRowDjs.isEmpty())
		{
			populateOutMember();
			populateSundayPledge();
			if (invertedMemberStatus.get(MSL.PI) != null)
				populateInHouse(new Vector<String>(invertedMemberStatus.get(MSL.PI)), false);
			if (this.pledgeAsActive && invertedMemberStatus.get(MSL.PO) != null)
				populateInHouse(new Vector<String>(invertedMemberStatus.get(MSL.PO)), false);
			if (invertedMemberStatus.get(MSL.UI) != null)
				populateInHouse(new Vector<String>(invertedMemberStatus.get(MSL.UI)), true);
			if (seatedDjCount++ % 2 < 1 &&  (invertedMemberStatus.get(MSL.SI) != null))
			{
				//seated dudes get DJ every other iteration
				// this line exists in case everyone in the house is magically seated
				// or we suck and don't have enough members
				populateInHouse(new Vector<String>(invertedMemberStatus.get(MSL.SI)), true); 
			}
		}
	}
	
	
	private void populateInHouse(Vector<String> seniority, boolean canHaveSunday) {
		
		if (columnRowDjs.isEmpty()) return;
		Object [] possibleCols = columnRowDjs.keySet().toArray();
		if (!canHaveSunday)
		{
			int sunIdx = -1;
			for (int o = 0; o < possibleCols.length; o++)
			{
				if (possibleCols[0].equals(new Integer(WEEKENDCOLIDX)))
				{
					sunIdx = o;
					break;
				}
			}
			if (sunIdx >= 0)
			{
				Object [] bufPossibleCols = new Object[possibleCols.length - 1];
				int bufIdx = 0;
				for (int o = 0; o < possibleCols.length; o++)
				{
					if (o != sunIdx)
						bufPossibleCols[bufIdx++] = possibleCols[o];
				}
				possibleCols = bufPossibleCols;
			}
		}
		
		int elimCols = 0;
		for (String m : seniority)
		{
			int colIdx = (int)(Math.random() * (possibleCols.length - elimCols));
			if (possibleCols.length - elimCols <= 0) break;
			int djCol = Integer.parseInt(possibleCols[colIdx].toString());
			Object [] possibleRows = columnRowDjs.get(djCol).toArray();
			int rowIdx = (int)(Math.random() * (possibleRows.length));
			int djRow = Integer.parseInt(possibleRows[rowIdx].toString());
			int currColSize = columnRowDjs.size();
			assignDj(djRow, djCol, m);
			if (currColSize > columnRowDjs.size())
			{
				swap(possibleCols, colIdx, columnRowDjs.size());
				elimCols++;
			}
		}
	}

	private void populateSundayPledge() 
	{
		if (columnRowDjs.get(WEEKENDCOLIDX) == null) return;
		Object [] weekendRows = columnRowDjs.get(WEEKENDCOLIDX).toArray();
		int numFilled = 0;
		HashSet<String> outMembers = new HashSet<String>();
		if (invertedMemberStatus.get(MSL.PO) != null )
			outMembers.addAll(invertedMemberStatus.get(MSL.PO));
		if (invertedMemberStatus.get(MSL.PI) != null )
			outMembers.addAll(invertedMemberStatus.get(MSL.PI));
		for (String member : outMembers)
		{
			int idx = (int)(Math.random() * (weekendRows.length - numFilled++));
			if (weekendRows.length - numFilled <= 0) break;
			int djRow = Integer.parseInt(weekendRows[idx].toString());
			swap(weekendRows, idx, weekendRows.length - numFilled);
			if (!markSundayAsPledge)
				assignDj(djRow, WEEKENDCOLIDX, member);
			else
				assignDj(djRow, WEEKENDCOLIDX, "Pledge");
		}
	}

	/**
	 * Out members work on Sundays and not on weekdays.
	 */
	private void populateOutMember() 
	{
		if (columnRowDjs.get(WEEKENDCOLIDX) == null) return;
		Object [] weekendRows = columnRowDjs.get(WEEKENDCOLIDX).toArray();
		int numFilled = 0;
		Vector<String> outMembers = invertedMemberStatus.get(MSL.UO);
		if (outMembers == null) return;
		for (String member : outMembers)
		{
			int idx = (int)(Math.random() * (weekendRows.length - numFilled++));
			if (weekendRows.length - numFilled <= 0) break;
			int djRow = Integer.parseInt(weekendRows[idx].toString());
			swap(weekendRows, idx, weekendRows.length - numFilled);
			assignDj(djRow, WEEKENDCOLIDX, member);
		}
	}

	/**
	 * Assigns DJ, and all its related calculations
	 * @param row
	 * @param col
	 * @param member
	 */
	private void assignDj(int row, int col, String member) 
	{
		djAssigned.put(row, col, member);
		columnRowDjs.get(col).remove(row);
		rowColumnDjs.get(row).remove(col);
		if (columnRowDjs.get(col).size() <= 0)
			columnRowDjs.remove(col);
		else if(rowColumnDjs.get(row).size() <= 0)
			rowColumnDjs.remove(row);
	}

	private void swap(Object[] elems, int first, int second) {
		// TODO Auto-generated method stub
		Object buf = elems[first];
		elems[first] = elems[second];
		elems[second] = elems[first];
	}

	private void populateRules(Workbook templateDj) {

		Sheet sheet = templateDj.getSheet(0);
		int endRowIdx = sheet.getRows();
		int endColIdx = sheet.getColumns();
		for (int r = this.STARTASSIGNROW; r < endRowIdx; r++)
		{
			for (int c = this.STARTASSIGNCOL; c < endColIdx; c++)
			{
				Cell currContent = sheet.getCell(c, r);
				String stringified = currContent.getContents();
				if (!stringified.trim().isEmpty())
				{
					rowColumnDjs.put(r, c);
					columnRowDjs.put(c, r);
					djAssigned.put(r, c, "");
				}
			}
		}
	}

	private void populateSeniority() 
	{
		String seniority = SaveLoad.loadFile(SENIORITYFILENAME);
		String [] seniorityLines = seniority.split("\n");
		MemberStatus currStatus = MemberStatus.SEATED;
		for (int l = 0; l < seniorityLines.length; l++)
		{
			if(seniorityLines[l].contains("!!!"))
			{
				if (seniorityLines[l].contains("unseated"))
					currStatus = MemberStatus.UNSEATED;
				else if (seniorityLines[l].contains("seated"))
					currStatus = MemberStatus.SEATED;
				else currStatus = MemberStatus.PLEDGE;
			}
			else
			{
				String [] currLine = seniorityLines[l].split(",");
				if (currLine.length == 2)
				{
					MemberLiving mlive;
					if (currLine[LIVESTATUSIDX].trim().equalsIgnoreCase("IN"))
						mlive = MemberLiving.IN;
					else
						mlive = MemberLiving.OUT;
					MSL ms = getMSL(currStatus, mlive);
					memberStatus.put(currLine[INITIALIDX], ms);
					invertedMemberStatus.put(ms, currLine[INITIALIDX]);
					memberSeniorityOrder.add(currLine[INITIALIDX]);
				}
			}
		}
		for (MSL stat : invertedMemberStatus.keySet())
		{
			Collections.reverse(invertedMemberStatus.get(stat));
		}
		easterEgg();
	}

	/**
	 * Intentionally written in functional programming paradigm to confuse those who do not 
	 * admire the elegancy of such beauty.
	 * @param currStatus member status
	 * @param mlive member living status
	 * @return
	 */
	private MSL getMSL(MemberStatus currStatus, MemberLiving mlive) {
		
		return (mlive == MemberLiving.IN) ?
				(	(currStatus == MemberStatus.PLEDGE) ?
							MSL.PI
				:	(currStatus == MemberStatus.SEATED) ?
							MSL.SI 
				: 			MSL.UI						)
				:	(currStatus == MemberStatus.PLEDGE) ?
							MSL.PO
				:	(currStatus == MemberStatus.SEATED) ?
							MSL.SO : MSL.UO;	
	}

	public void saveFiles()
	{
		
	}
	
	public void saveExcelFileToFolder(String dir)
	{
		
	}
	

	private void easterEgg() {
		if (memberStatus.size() <= 0)
		{
			try {
				throw new Exception("!!! ARE YOU THE GHOST OF 1812??? " +
						"HOW CAN YOU BE ASSIGNING DJs WHEN NO ONE IS LIVING IN THE HOUSE???");
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				System.exit(-1);
			}
		}
		else if (!invertedMemberStatus.containsKey(MSL.PI)
				&& !invertedMemberStatus.containsKey(MSL.UI)
				&& !invertedMemberStatus.containsKey(MSL.SI) )
		{
			try {
				throw new Exception("!!! NO ONE IS LIVING IN THE HOUSE??? " +
						"WHAT ARE YOU, YOU DUMB SHIT????");
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				System.exit(-1);
			}
		}
	}
	
}
